package link.jfire.simplerpc.client;

import java.nio.charset.Charset;
import java.util.concurrent.Future;
import link.jfire.baseutil.collection.ByteCache;
import link.jfire.baseutil.simplelog.ConsoleLogFactory;
import link.jfire.baseutil.simplelog.Logger;
import link.jfire.fose.Fose;
import link.jfire.fose.util.IOUtil;
import link.jfire.simplerpc.server.messagehandler.InvokeType;
import link.jfire.socket.socketclient.Client;
import link.jfire.socket.socketclient.filter.DataFilter;
import link.jfire.socket.socketserver.util.DefaultHeadFactory;
import link.jfire.socket.socketserver.util.HeadFactory;

public class BytecodeInvoker
{
    protected HeadFactory            headFactory         = new DefaultHeadFactory();
    protected long                   readTimeout         = 3000;
    protected long                   reuseChannelTimeout = 55000;
    protected String                 ip;
    protected int                    port;
    protected static Charset         charset             = Charset.forName("utf8");
    protected byte[]                 proxyNameBytes;
    protected int                    proxyNameLength;
    protected DataFilter[]           dataFilters         = new DataFilter[0];
    protected ThreadLocal<Fose>      lbseLocal           = new ThreadLocal<Fose>() {
        @Override
        protected Fose initialValue()
        {
            return new Fose();
        }
    };
    protected ThreadLocal<ByteCache> cacheLocal          = new ThreadLocal<ByteCache>() {
        @Override
        protected ByteCache initialValue()
        {
            return new ByteCache();
        }
    };
    protected ThreadLocal<Client>    clientChanelLocal   = new ThreadLocal<Client>() {
        @Override
        protected Client initialValue()
        {
            Client client = new Client(RpcResult.getInstance());
            client.setIp(ip).setPort(port).setReadTimeout(readTimeout).setReuseChannelTimeout(reuseChannelTimeout).setDataFilters(dataFilters).setHeadFactory(headFactory);
            return client;
        }
    };
    private static Logger            logger              = ConsoleLogFactory.getLogger();
    
    public Object invoke(String methodName, Object[] args) throws Throwable
    {
        ByteCache cache = cacheLocal.get();
        Fose lbse = lbseLocal.get();
        prepareData(lbse, methodName, args, cache);
        Client clientUtil = clientChanelLocal.get();
        Future<Object> future = clientUtil.sendData(InvokeType.METHOD_INVOKE, cache);
        logger.debug("发送rpc调用数据");
        Object result = future.get();
        logger.debug("获得rpc调用结果成功");
        return result;
    }
    
    /**
     * 准备需要发送的数据,将数据按照规定的格式填充到buffer中. 返回填充完毕的buffer
     * 
     * @param buffer
     * @param method
     * @param args
     */
    protected void prepareData(Fose lbse, String methodName, Object[] args, ByteCache cache)
    {
        cache.clear();
        // 首先写入代理名的长度
        IOUtil.writeInt(proxyNameLength, cache);
        // 写入代理名称
        cache.putArray(proxyNameBytes);
        // 写入方法名的长度
        byte[] methodNameBytes = methodName.getBytes(charset);
        IOUtil.writeInt(methodNameBytes.length, cache);
        // 方法名
        cache.putArray(methodNameBytes);
        int argsNum = args == null ? 0 : args.length;
        // 写入参数个数
        IOUtil.writeInt(argsNum, cache);
        // 逐个写入参数
        for (int i = 0; i < argsNum; i++)
        {
            ByteCache tmp = lbse.serialize(args[i]);
            cache.putByteCache(tmp);
        }
    }
    
    public BytecodeInvoker setProxyName(String proxyName)
    {
        proxyNameBytes = proxyName.getBytes(charset);
        proxyNameLength = proxyNameBytes.length;
        return this;
    }
    
    public BytecodeInvoker setReadTimeout(final long readTimeout)
    {
        this.readTimeout = readTimeout;
        clientChanelLocal = new ThreadLocal<Client>() {
            @Override
            protected Client initialValue()
            {
                Client client = new Client(RpcResult.getInstance());
                client.setIp(ip).setPort(port).setReadTimeout(readTimeout).setReuseChannelTimeout(reuseChannelTimeout).setDataFilters(dataFilters).setHeadFactory(headFactory);
                return client;
            }
        };
        return this;
    }
    
    public BytecodeInvoker setReuseChannelTimeout(final long reuseChannelTimeout)
    {
        this.reuseChannelTimeout = reuseChannelTimeout;
        clientChanelLocal = new ThreadLocal<Client>() {
            @Override
            protected Client initialValue()
            {
                Client client = new Client(RpcResult.getInstance());
                client.setIp(ip).setPort(port).setReadTimeout(readTimeout).setReuseChannelTimeout(reuseChannelTimeout).setDataFilters(dataFilters).setHeadFactory(headFactory);
                return client;
            }
        };
        return this;
    }
    
    public BytecodeInvoker setIp(final String ip)
    {
        this.ip = ip;
        clientChanelLocal = new ThreadLocal<Client>() {
            @Override
            protected Client initialValue()
            {
                Client client = new Client(RpcResult.getInstance());
                client.setIp(ip).setPort(port).setReadTimeout(readTimeout).setReuseChannelTimeout(reuseChannelTimeout).setDataFilters(dataFilters).setHeadFactory(headFactory);
                return client;
            }
        };
        return this;
    }
    
    public BytecodeInvoker setPort(final int port)
    {
        this.port = port;
        clientChanelLocal = new ThreadLocal<Client>() {
            @Override
            protected Client initialValue()
            {
                Client client = new Client(RpcResult.getInstance());
                client.setIp(ip).setPort(port).setReadTimeout(readTimeout).setReuseChannelTimeout(reuseChannelTimeout).setDataFilters(dataFilters).setHeadFactory(headFactory);
                return client;
            }
        };
        return this;
    }
    
    public BytecodeInvoker setDataFilters(final DataFilter... dataFilters)
    {
        if (dataFilters == null)
        {
            this.dataFilters = new DataFilter[0];
        }
        else
        {
            this.dataFilters = dataFilters;
        }
        clientChanelLocal = new ThreadLocal<Client>() {
            @Override
            protected Client initialValue()
            {
                Client client = new Client(RpcResult.getInstance());
                client.setIp(ip).setPort(port).setReadTimeout(readTimeout).setReuseChannelTimeout(reuseChannelTimeout).setDataFilters(BytecodeInvoker.this.dataFilters).setHeadFactory(headFactory);
                return client;
            }
        };
        return this;
    }
    
    public BytecodeInvoker setHeadFactory(final HeadFactory headFactory)
    {
        this.headFactory = headFactory;
        clientChanelLocal = new ThreadLocal<Client>() {
            @Override
            protected Client initialValue()
            {
                Client client = new Client(RpcResult.getInstance());
                client.setIp(ip).setPort(port).setReadTimeout(readTimeout).setReuseChannelTimeout(reuseChannelTimeout).setDataFilters(dataFilters).setHeadFactory(headFactory);
                return client;
            }
        };
        return this;
    }
    
    public void close()
    {
        clientChanelLocal.get().close();
        lbseLocal.remove();
        cacheLocal.remove();
        clientChanelLocal.remove();
    }
    
}
